<template>
  <div>
    <div class="workspace">
      <div class="jianpu">
        <svg width="1000" height="400">
          <g
            v-for="(barGroup, row) in barGroups"
            :key="row"
            :transform="'translate(0,' + 60 * row + ')'"
          >
            <g
              v-for="(barInfo, outerCol) in barGroup"
              :key="outerCol"
              :transform="'translate(' + 40 * barInfo.noteCount + ', 0)'"
            >
              <g
                v-for="(note, col) in barInfo.bar"
                :key="col"
                :transform="'translate(' + 40 * col + ', 90)'"
              >
                <text
                  v-if="note.accidentalText === '#'"
                  class="accidental-text"
                  x="0"
                  y="80"
                >
                  ♯
                </text>
                <text
                  v-if="note.accidentalText === '$'"
                  class="accidental-text"
                  x="0"
                  y="80"
                >
                  ♭
                </text>
                <text v-if="note.number !== '-'" :class="note" x="10" y="85">
                  {{ note.number }}
                </text>
                <line v-else class="tie" x1="10" y1="78" x2="22" y2="78" />
                <circle
                  v-for="i of note.octave > 0 ? note.octave : 0"
                  :key="'hight' + i"
                  class="octave octave-high"
                  cx="15"
                  :cy="75 - i * 6"
                  r="2"
                />
                <circle
                  v-for="i of note.octave < 0 ? -note.octave : 0"
                  :key="'low' + i"
                  class="octave octave-low"
                  cx="15"
                  :cy="83 + note.duration * 3 + i * 6"
                  r="2"
                />
                <line
                  v-for="line of note.duration"
                  :key="line"
                  class="underline"
                  x1="7"
                  :y1="85 + line * 3"
                  :x2="note.dot ? 30 : 22"
                  :y2="85 + line * 3"
                />
                <circle v-if="note.dot" class="dot" cx="25" cy="78" r="2" />
                <text class="lyrics" x="10" y="135"></text>
                <line
                  v-if="col == barInfo.bar.length - 1"
                  class="underline"
                  x1="30"
                  y1="60"
                  x2="30"
                  y2="100"
                />
              </g>
            </g>
          </g>
        </svg>
      </div>

      <div>
        <el-button type="primary" icon="el-icon-caret-right" @click="play()"
          >播放</el-button
        >
        <el-button
          type="primary"
          icon="el-icon-sort"
          class="transform-button"
          @click="transform()"
          >F转调C</el-button
        >
        <a
          href="/help"
          target="_blank"
          class="el-button el-button--primary is-plain"
        >
          帮助
        </a>
      </div>

      <div style="margin: 5px 0px">
        <el-select
          v-model="currInstrumentName"
          placeholder="请选择乐器"
          @change="changeInstrument"
        >
          <el-option
            v-for="(item, i) in instruments"
            :key="i"
            :label="item.label"
            :value="item.name"
          />
        </el-select>
      </div>

      <div>
        <div class="base-pitch-area">
          <span class="base-pitch-label">基础音高:</span>
          <el-input
            v-model.number="centerPitch"
            class="base-pitch-input"
            placeholder="4-6"
          ></el-input>
        </div>
        <div class="beat-area">
          <span class="beat-label">节拍:</span>
          <el-input v-model.number="jianpu.beat" class="beat-input"></el-input>
        </div>
        <AutoSizeTextArea v-model="jianpu.noteScript" />
      </div>
    </div>
  </div>
</template>

<script>
import Tone from 'tone'
import AutoSizeTextArea from '@/components/AutoSizeTextArea'

export default {
  components: {
    AutoSizeTextArea
  },
  data() {
    return {
      instrument: null,
      pageWidth: 600,
      maxNotePerLine: 20,
      centerPitch: 5,
      pitchNames: ['0', 'C', 'D', 'E', 'F', 'G', 'A', 'B'],
      editorSize: { minRows: 4, maxRows: 6 },
      editorMode: 'simple',
      editorModes: [{ name: 'simple', label: '简单模式' }],
      transformOn: false,
      /* C大调每个音距离首音的半音程 */
      cmajorHalfPitchInterval: [0, 2, 4, 5, 7, 9, 11],
      cmajorHalfIntervalPitch: [
        { number: 1 },
        { number: 1, accidentalText: '#' },
        { number: 2 },
        { number: 2, accidentalText: '#' },
        { number: 3 },
        { number: 4 },
        { number: 4, accidentalText: '#' },
        { number: 5 },
        { number: 5, accidentalText: '#' },
        { number: 6 },
        { number: 6, accidentalText: '#' },
        { number: 7 }
      ],
      currInstrumentName: 'guitar',
      instruments: [
        {
          name: 'harp',
          label: '竖琴',
          source: {
            A4: 'A4.mp3',
            A6: 'A6.mp3',
            B3: 'B3.mp3',
            B5: 'B5.mp3',
            B6: 'B6.mp3',
            C3: 'C3.mp3',
            C5: 'C5.mp3',
            D4: 'D4.mp3',
            D6: 'D6.mp3',
            E3: 'E3.mp3',
            E5: 'E5.mp3',
            F4: 'F4.mp3',
            F6: 'F6.mp3',
            G3: 'G3.mp3',
            G5: 'G5.mp3'
          },
          setting: {
            release: 1,
            baseUrl: '../../../harp/'
          }
        },
        {
          name: 'piano',
          label: '钢琴',
          source: {
            A0: 'A0.mp3',
            C1: 'C1.mp3',
            'D#1': 'Ds1.mp3',
            'F#1': 'Fs1.mp3',
            A1: 'A1.mp3',
            C2: 'C2.mp3',
            'D#2': 'Ds2.mp3',
            'F#2': 'Fs2.mp3',
            A2: 'A2.mp3',
            C3: 'C3.mp3',
            'D#3': 'Ds3.mp3',
            'F#3': 'Fs3.mp3',
            A3: 'A3.mp3',
            C4: 'C4.mp3',
            'D#4': 'Ds4.mp3',
            'F#4': 'Fs4.mp3',
            A4: 'A4.mp3',
            C5: 'C5.mp3',
            'D#5': 'Ds5.mp3',
            'F#5': 'Fs5.mp3',
            A5: 'A5.mp3',
            C6: 'C6.mp3',
            'D#6': 'Ds6.mp3',
            'F#6': 'Fs6.mp3',
            A6: 'A6.mp3',
            C7: 'C7.mp3',
            'D#7': 'Ds7.mp3',
            'F#7': 'Fs7.mp3',
            A7: 'A7.mp3',
            C8: 'C8.mp3'
          },
          setting: {
            release: 0.1,
            baseUrl: '../../../piano/'
          }
        },
        {
          name: 'guitar',
          label: '吉他',
          source: {
            'F#2': 'Fs2.mp3',
            A2: 'A2.mp3',
            'F#3': 'Fs3.mp3',
            A3: 'A3.mp3',
            'D#4': 'Ds4.mp3',
            'F#4': 'Fs4.mp3',
            A4: 'A4.mp3',
            'F#5': 'Fs5.mp3',
            A5: 'A5.mp3'
          },
          setting: {
            release: 1,
            baseUrl: '../../../guitar/'
          }
        },
        {
          name: 'harmonica',
          label: '口琴',
          source: {
            C4: 'C4.[wav]'
          },
          setting: {
            release: 1,
            baseUrl: '../../../harmonica/'
          }
        }
      ],
      jianpu: {
        titles: [],
        composer: [],
        meter: '',
        beat: 90,
        k: '',
        noteScript:
          '6, - 6,| 3 - 3 | 7, 1 7,| 6, - - | 0 3 5 | 6 - 5 | 3 #4 2 |3 - - |6 6 - 6 | 5 - 3| 3 2 1| 7, 5, -  |6, - 3 | 2 - 1 | 7, 6, 5,| 6, - - |6 6 - 6| 5 - 3 | 3 2 1| 7, 5, - - |6, - 3| 2 - 1 | 7, 6, 5, | 6, - -'
        // "5/ 5/ 5/ 5/ 3/ 4/ 5. 1'. | 6/ 6/ 6/ 6/ 4/ 6/ 5. - 0 | 5/ 5/ 5/ 5/ 7/ 6/ 4. 4 | 4/ 4/ 4/ 4/ 3./ 2// 1."
      }
    }
  },
  computed: {
    bars() {
      const bars = []
      const barScripts = this.jianpu.noteScript.trim().split('|')
      barScripts.forEach((barScript) => {
        const originalNotes = barScript.split(/\s+/g)
        const notes = []
        originalNotes.forEach((originalNote) => {
          if (!this.transformOn) {
            const note = {}

            const matchedNumber = originalNote.match(/\d|-/)

            if (matchedNumber == null) {
              return
            }

            note.number = matchedNumber[0]

            // 延音线无其他属性
            if (note.number === '-') {
              notes.push(note)
              return
            }

            const accidentalTextMatched = originalNote.match(/#|\$/)
            note.accidentalText =
              accidentalTextMatched == null ? null : accidentalTextMatched[0]

            const highOctave = originalNote.match(/'/g)
            const lowOctave = originalNote.match(/,/g)
            note.octave =
              (highOctave == null ? 0 : highOctave.length) -
              (lowOctave == null ? 0 : lowOctave.length)

            note.duration =
              originalNote.match(/\//g) == null
                ? 0
                : originalNote.match(/\//g).length

            note.dot = originalNote.indexOf('.') > 0

            notes.push(note)
          } else {
            const note = {}

            const dashMatched = originalNote.match(/-/)

            // 延音线无其他属性
            if (dashMatched != null) {
              note.number = '-'
              notes.push(note)
              return
            }

            const matchedNumber = originalNote.match(/\d/)

            if (matchedNumber == null) {
              return
            }

            note.number = matchedNumber[0]

            note.duration =
              originalNote.match(/\//g) == null
                ? 0
                : originalNote.match(/\//g).length

            note.dot = originalNote.indexOf('.') > 0

            if (note.number === '0') {
              notes.push(note)
              return
            }

            // TODO: 判断调式
            const interval = 5 + this.cmajorHalfPitchInterval[note.number - 1]
            note.interval = interval

            const sharpMatched = originalNote.match(/#/)
            const flatMatched = originalNote.match(/\$/)

            if (sharpMatched != null) {
              note.interval = note.interval + 1
            } else if (flatMatched != null) {
              note.interval = note.interval - 1
            }

            const highOctave = originalNote.match(/'/g)
            const lowOctave = originalNote.match(/,/g)
            note.interval =
              note.interval +
              (highOctave == null ? 0 : highOctave.length * 12) -
              (lowOctave == null ? 0 : lowOctave.length * 12)

            note.number = this.cmajorHalfIntervalPitch[
              note.interval % 12
            ].number
            note.accidentalText = this.cmajorHalfIntervalPitch[
              note.interval % 12 >= 0
                ? note.interval % 12
                : (note.interval % 12) + 12
            ].accidentalText

            note.octave =
              note.interval < 0
                ? parseInt(note.interval / 12) - 1
                : parseInt(note.interval / 12)

            notes.push(note)
          }
        })
        if (notes.length > 0) {
          bars.push(notes)
        }
      })

      return bars
    },
    barGroups() {
      const barGroups = []
      let noteCount = 0
      let barGroup = []
      this.bars.forEach((bar, i) => {
        barGroup.push({ bar, noteCount })
        noteCount += bar.length

        if (noteCount >= this.maxNotePerLine || i === this.bars.length - 1) {
          barGroups.push(barGroup)

          barGroup = []
          noteCount = 0
        }
      })

      return barGroups
    },
    parts() {
      const parts = []

      // 前面的音符的累计时长
      let time = 0

      // TODO: 读取拍子,当前默认为四拍子
      const measure = 60 / this.jianpu.beat
      this.bars.forEach((bar, i) => {
        bar.forEach((note, j) => {
          if (note.number === '-') {
            parts[parts.length - 1].duration += measure
            time += measure

            // 末尾为延音符，将最后一个音符设置为末尾
            parts[parts.length - 1].end =
              i === this.bars.length - 1 && j === bar.length - 1
            return
          }

          let duration = measure / (note.duration ? note.duration + 1 : 1)

          // 如果有附点音符，时值乘以1.5
          if (note.dot) {
            duration *= 1.5
          }

          // TODO: 处理降调符号
          parts.push({
            time,
            note:
              this.pitchNames[note.number] +
              (note.accidentalText ? note.accidentalText : '') +
              (this.centerPitch + note.octave),

            duration,
            end: i === this.bars.length - 1 && j === bar.length - 1
          })

          // 累计时长
          time += duration
        })
      })
      return parts
    }
  },
  mounted() {
    const loadCover = this.$loading({ fullscreen: true })

    this.instrument = new Tone.Sampler(
      {
        'F#2': 'Fs2.mp3',
        A2: 'A2.mp3',
        'F#3': 'Fs3.mp3',
        A3: 'A3.mp3',
        'D#4': 'Ds4.mp3',
        'F#4': 'Fs4.mp3',
        A4: 'A4.mp3',
        'F#5': 'Fs5.mp3',
        A5: 'A5.mp3'
      },
      {
        release: 1,
        baseUrl: '../../../guitar/'
      }
    ).toMaster()

    const interval = setInterval(() => {
      if (this.instrument.loaded) {
        clearInterval(interval)
        loadCover.close()
      }
    }, 500)
  },
  methods: {
    play() {
      // const synth = new Tone.Synth().toMaster()

      const instrument = this.instrument

      Tone.Transport.stop()
      Tone.Transport.bpm.value = this.jianpu.beat

      const part = new Tone.Part(function(time, value) {
        instrument.triggerAttackRelease(value.note, value.duration)
        if (value.end) {
          part.dispose()
          Tone.Transport.stop()
        }
      }, this.parts)

      part.start(0)

      Tone.Transport.start()
    },
    changeInstrument() {
      const loadCover = this.$loading({ fullscreen: true })

      this.instrument.disconnect()
      this.instruments.forEach((i) => {
        if (i.name === this.currInstrumentName) {
          this.instrument = new Tone.Sampler(i.source, i.setting).toMaster()
        }
      })

      const interval = setInterval(() => {
        if (this.instrument.loaded) {
          clearInterval(interval)
          loadCover.close()
        }
      }, 500)
    },
    transform() {
      this.transformOn = !this.transformOn
    }
  },
  head() {
    return {
      title: '编曲'
    }
  }
}
</script>

<style>
.workspace {
  margin: 0 auto;
  width: 800px;
}
.jianpu {
  width: 100%;
  height: 40%;
}
.editor {
  width: 100%;
}
.note {
}
.accidental-text {
  font-size: 12px;
}
.underline {
  stroke: rgb(0, 0, 0);
  stroke-width: 2;
}
.tie {
  stroke: rgb(0, 0, 0);
  stroke-width: 2;
}
.base-pitch-area {
  display: flex;
  align-items: center;
  margin: 5px 0px;
}
.base-pitch-label {
  width: 80px;
}
.base-pitch-input {
  width: 60px;
}
.beat-area {
  display: flex;
  align-items: center;
  margin: 5px 0px;
}
.beat-label {
  width: 80px;
}
.beat-input {
  width: 60px;
}
.transform-button i {
  transform: rotate(90deg);
}
</style>
